using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Web;
using System.Security.Permissions;
using System.Web.Util;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.PKS;

namespace PKS.Sharepoint.Navigation
{

    /// <summary>
    /// Navigation Provider for the Left Navigation in the 
    /// Podcasting Kit for SharePoint.
    /// </summary>
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class SPLeftNavigationProvider : SiteMapProvider
    {

        #region "    Private Fields    "

        private SiteMapNode _siteMapNode;            // The Root SiteMapNode
        private Hashtable _childNodeCollectionTable; // Holds all the child nodespass
        private Hashtable _keyTable;                 // Holds all the keys
        private Hashtable _parentNodeTable;          // Holds all the parent nodes
        private Hashtable _urlTable;                 // Holds all the URLs
        private Object _lock;                        // For performing locks
        private string _siteMapList;                 // The list that hold navigation info
        private bool _initialized;                   // If this instance has been initialized
        private bool _isMobileView = false;          // If this instance is for the Mobile Pages
        private int timeout = 300;                   // how long to cache data in seconds (5 minutes)
        private TimeSpan lastRefresh = new TimeSpan(0, 0, 0); // holds the time of the last refresh

        private const string _siteMapListAttribute = "siteMapList";
        private const string _isMobileViewAttribute = "isMobileView";
        private const string _seriesType = "Series";
        private const string _categoryType = "Category";
        private const string _displayOrder = "Display Order";
        private const string _displayName = "Display Name";
        private const string _listUrl = "List Url";
        private const string _navigationType = "Navigation Type";
        private const string _filterBy = "Filter by ";
        private const string _addCategoryFilter = "javascript:addCategoryFilter('{0}', '{1}');";
        private string[] _dateFilterTitles = { "All", "Today", "ThisWeek", "ThisMonth", "ThisYear" };
        private string[] _dateFilterNames = { "All", "Today", "This Week", "This Month", "This Year" };

        private Dictionary<string, Guid> _cachedLists = new Dictionary<string, Guid>(); // cached guids for specific lists
        private Dictionary<string, Guid> _cachedFields = new Dictionary<string, Guid>(); // cached guids for specific fields

        #endregion

        #region "    Nested Structure  "

        protected class MainNode
        {
            private SiteMapNode _node;
            private string _title;
            private string _type;
            private int _index;

            public SiteMapNode Node { get { return _node; } }
            public string Title { get { return _title; } }
            public string Type { get { return _type; } }
            public int Index { get { return _index; } }

            public MainNode(int index, string title, string type, SiteMapNode node)
            {
                _node = node;
                _title = title;
                _type = type;
                _index = index;
            }
        }

        #endregion

        #region "    Constructor       "

        /// <summary>
        /// Constructs a new instance of SPLeftNavigationProvider.
        /// </summary>
        public SPLeftNavigationProvider()
        {
            _lock = new object();
        }

        #endregion

        #region "    Public Methods    "

        /// <summary>
        /// Overridden Initialize method from the SiteMapProvider class. Gets custom attributes from the web.config file.
        /// </summary>
        /// <param name="name">The name of this instance from the web.config file.</param>
        /// <param name="attributes">The list of attributes from the web.config file.</param>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes)
        {
            if (_initialized) throw new InvalidOperationException("The CustomNavigationProvider was initialized more than once.");

            _siteMapList = attributes.Get(_siteMapListAttribute);
            if (string.IsNullOrEmpty(_siteMapList)) _siteMapList = "Navigation";
            string mobile = attributes.Get(_isMobileViewAttribute);
            if (mobile.ToLower() == "true") _isMobileView = true;

            base.Initialize(name, attributes);
            _initialized = true;
        }

        /// <summary>
        /// Builds the Site Map.
        /// </summary>
        /// <returns>The SiteMapNode containing all the navigation nodes.</returns>
        public SiteMapNode BuildSiteMap()
        {
            // check cache timeout
            TimeSpan now = new TimeSpan(DateTime.Now.Ticks);
            if (_siteMapNode != null && (now - lastRefresh).TotalSeconds < timeout)
            {
                return _siteMapNode;
            }
            lastRefresh = now;

            lock (_lock)
            {
                this.Clear();
                _siteMapNode = new SiteMapNode(this, "mapRoot", string.Empty, string.Empty, string.Empty);

                List<MainNode> navList = ParseMainList(_siteMapList);
                foreach (MainNode node in navList)
                {
                    string title = node.Title;
                    string type = node.Type;
                    if (type == _seriesType || !_isMobileView)
                    {
                        AddNode(node.Node, _siteMapNode);
                    }

                    List<SiteMapNode> subNodes;
                    if (type == _seriesType)
                    {
                        subNodes = ParseSeriesList(title);
                    }
                    else if (type == _categoryType && !_isMobileView)
                    {
                        subNodes = ParseCategoryItem(title);
                    }
                    else continue;
                    foreach (SiteMapNode subNode in subNodes)
                    {
                        AddNode(subNode, node.Node);
                    }
                }
            }
            return _siteMapNode;
        }

        /// <summary>
        /// Finds a site map node by URL.
        /// </summary>
        /// <param name="rawUrl">The URL to look up.</param>
        /// <returns>The found Node or NULL.</returns>
        public override SiteMapNode FindSiteMapNode(string rawUrl)
        {
            if (rawUrl == null)
            {
                throw new ArgumentNullException("rawUrl");
            }
            rawUrl = rawUrl.Trim();
            if (rawUrl.Length == 0)
            {
                return null;
            }
            //if (UrlPath.IsAppRelativePath(rawUrl))
            //{
            //    rawUrl = UrlPath.MakeVirtualPathAppAbsolute(rawUrl);
            //}
            this.BuildSiteMap();
            return (SiteMapNode)this.UrlTable[rawUrl];
        }

        /// <summary>
        /// Finds a site map nobe by it's key.
        /// </summary>
        /// <param name="key">The key for the Node.</param>
        /// <returns>The found Node or NULL.</returns>
        public override SiteMapNode FindSiteMapNodeFromKey(string key)
        {
            SiteMapNode node = base.FindSiteMapNodeFromKey(key);
            if (node == null)
            {
                node = (SiteMapNode)this.KeyTable[key];
            }
            return node;
        }

        /// <summary>
        /// Gets the child nodes of a node.
        /// </summary>
        /// <param name="node">The node to get the children of.</param>
        /// <returns>A SiteMapNodeCollection of the node's children.</returns>
        public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            this.BuildSiteMap();
            SiteMapNodeCollection collection = (SiteMapNodeCollection)this.ChildNodeCollectionTable[node];
            if (collection == null)
            {
                SiteMapNode node2 = (SiteMapNode)this.KeyTable[node.Key];
                if (node2 != null)
                {
                    collection = (SiteMapNodeCollection)this.ChildNodeCollectionTable[node2];
                }
            }
            if (collection == null)
            {
                return new SiteMapNodeCollection();
            }
            if (!base.SecurityTrimmingEnabled)
            {
                return SiteMapNodeCollection.ReadOnly(collection);
            }
            HttpContext current = HttpContext.Current;
            SiteMapNodeCollection nodes2 = new SiteMapNodeCollection(collection.Count);
            foreach (SiteMapNode node3 in collection)
            {
                if (node3.IsAccessibleToUser(current))
                {
                    nodes2.Add(node3);
                }
            }
            return SiteMapNodeCollection.ReadOnly(nodes2);
        }

        /// <summary>
        /// Gets the parent of a node.
        /// </summary>
        /// <param name="node">The node to get the parent of.</param>
        /// <returns>The Parent SiteMapNode.</returns>
        public override SiteMapNode GetParentNode(SiteMapNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            this.BuildSiteMap();
            SiteMapNode parentNode = (SiteMapNode)this.ParentNodeTable[node];
            if (parentNode == null)
            {
                SiteMapNode node3 = (SiteMapNode)this.KeyTable[node.Key];
                if (node3 != null)
                {
                    parentNode = (SiteMapNode)this.ParentNodeTable[node3];
                }
            }
            if ((parentNode == null) && (this.ParentProvider != null))
            {
                parentNode = this.ParentProvider.GetParentNode(node);
            }
            return parentNode;
        }

        #endregion

        #region "    Protected Methods "

        protected override SiteMapNode GetRootNodeCore()
        {
            this.BuildSiteMap();
            return _siteMapNode;
        }

        protected override void AddNode(SiteMapNode node, SiteMapNode parentNode)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            lock (_lock)
            {
                bool flag = false;
                string url = node.Url;
                if (!string.IsNullOrEmpty(url))
                {
                    // Took this out so that the root nodes to point to nothing or home
                    // until the Series collection is set up.
                    //if (HttpRuntime.AppDomainAppVirtualPath != null)
                    //{
                    //    if (this.UrlTable[url] != null)
                    //    {
                    //        throw new InvalidOperationException("The URL is already registered with the Navigation Provider.");
                    //    }
                    //}
                    flag = true;
                }
                string key = node.Key;
                if (this.KeyTable.Contains(key))
                {
                    throw new InvalidOperationException(
                        string.Format("The Item, '{0}' is already registered with the Navigation Provider.", key));
                }
                this.KeyTable[key] = node;
                if (flag)
                {
                    this.UrlTable[url] = node;
                }
                if (parentNode != null)
                {
                    this.ParentNodeTable[node] = parentNode;
                    if (this.ChildNodeCollectionTable[parentNode] == null)
                    {
                        this.ChildNodeCollectionTable[parentNode] = new SiteMapNodeCollection();
                    }
                    ((SiteMapNodeCollection)this.ChildNodeCollectionTable[parentNode]).Add(node);
                }
            }
        }

        protected List<MainNode> ParseMainList(string listName)
        {
            List<MainNode> nodes = new List<MainNode>();
            SPList list = FindList(listName);
            if (list == null) return nodes;

            for (int i = 0; i < list.Items.Count; i++)
            {
                SPListItem item = list.Items[i];
                int index = Convert.ToInt32(item[_displayOrder]);
                string title = item.Title;
                string name = item[_displayName].ToString();
                string url = (item[_listUrl] != null) ? item[_listUrl].ToString() : string.Empty;
                string type = item[_navigationType].ToString();
                string desc = _filterBy + name;
                url = CleanSharepointUrl(url);

                nodes.Add(new MainNode(index, title, type, new SiteMapNode(this, name, url, name, desc)));
            }
            // order the nodes by their index
            nodes.Sort(new Comparison<MainNode>(
                delegate(MainNode x, MainNode y) { return x.Index.CompareTo(y.Index); }));

            return nodes;
        }

        protected List<SiteMapNode> ParseSeriesList(string listName)
        {
            List<SiteMapNode> nodes = new List<SiteMapNode>();
            SPList list = FindList(listName);
            if (list == null) return nodes;

            for (int i = 0; i < list.Items.Count; i++)
            {
                SPListItem item = list.Items[i];
                string name = item.DisplayName;
                string url;
                if (_isMobileView)
                {
                    url = string.Format(_addCategoryFilter, _seriesType, name);
                }
                else
                {
                    url = item.Url;
                    url = CleanSharepointUrl(url);
                }
                nodes.Add(new SiteMapNode(this, name, url, name, name));
            }

            return nodes;
        }

        protected List<SiteMapNode> ParseCategoryItem(string itemName)
        {
            List<SiteMapNode> nodes = new List<SiteMapNode>();
            SPField field = null;
            if (_cachedFields.ContainsKey(itemName))
            {
                    field = Web.Fields[_cachedFields[itemName]];
            }
            else
            {
                for (int i = 0; i < Web.Fields.Count; i++)
                {
                    if (Web.Fields[i].StaticName == itemName)
                    {
                        field = Web.Fields[i];
                        _cachedFields.Add(itemName, field.Id);
                        break;
                    }
                }
            }

            if (field == null) return nodes;
            if (field is SPFieldChoice)
            {
                SPFieldChoice fChoice = (SPFieldChoice)field;
                for (int j = 0; j < fChoice.Choices.Count; j++)
                {
                    string name = fChoice.Choices[j];
                    string filterName = field.StaticName;
                    string url = string.Format(_addCategoryFilter, filterName, name);
                    nodes.Add(new SiteMapNode(this, name, url, name, name));
                }
            }
            else if (field is SPFieldMultiChoice)
            {
                SPFieldMultiChoice fChoice = (SPFieldMultiChoice)field;
                for (int j = 0; j < fChoice.Choices.Count; j++)
                {
                    string name = fChoice.Choices[j];
                    string filterName = field.StaticName;
                    string url = string.Format(_addCategoryFilter, filterName, name);
                    nodes.Add(new SiteMapNode(this, name, url, name, name));
                }
            }
            else if (field is SPFieldDateTime)
            {
                string filterName = field.StaticName;
                for (int i = 0; i < _dateFilterNames.Length; i++)
                {
                    string url = string.Format(_addCategoryFilter, filterName, _dateFilterTitles[i]);
                    string name = _dateFilterNames[i];
                    nodes.Add(new SiteMapNode(this, name, url, name, name));
                }
            }

            return nodes;
        }

        protected SPList FindList(string listName)
        {
            if (_cachedLists.ContainsKey(listName))
            {
                return Web.Lists[_cachedLists[listName]];
            }
            for (int i = 0; i < Web.Lists.Count; i++)
            {
                if (string.Compare(Web.Lists[i].Title, listName, true) == 0)
                {
                    _cachedLists.Add(listName, Web.Lists[i].ID);
                    return Web.Lists[i];
                }
            }
            return null;
        }

        protected void Clear()
        {
            lock (_lock)
            {
                if (this._childNodeCollectionTable != null)
                {
                    this._childNodeCollectionTable.Clear();
                }
                if (this._urlTable != null)
                {
                    this._urlTable.Clear();
                }
                if (this._parentNodeTable != null)
                {
                    this._parentNodeTable.Clear();
                }
                if (this._keyTable != null)
                {
                    this._keyTable.Clear();
                }
            }
        }

        protected override void RemoveNode(SiteMapNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            lock (_lock)
            {
                SiteMapNode node2 = (SiteMapNode)this.ParentNodeTable[node];
                if (this.ParentNodeTable.Contains(node))
                {
                    this.ParentNodeTable.Remove(node);
                }
                if (node2 != null)
                {
                    SiteMapNodeCollection nodes = (SiteMapNodeCollection)this.ChildNodeCollectionTable[node2];
                    if ((nodes != null) && nodes.Contains(node))
                    {
                        nodes.Remove(node);
                    }
                }
                string url = node.Url;
                if (((url != null) && (url.Length > 0)) && this.UrlTable.Contains(url))
                {
                    this.UrlTable.Remove(url);
                }
                string key = node.Key;
                if (this.KeyTable.Contains(key))
                {
                    this.KeyTable.Remove(key);
                }
            }
        }

        protected string CleanSharepointUrl(string url)
        {
            string appPath = Web.Url;
            if (!appPath.EndsWith("/"))
            {
                appPath += "/";
            }
            if (url.Length == 1 && url[0] == '~')
            {
                return appPath;
            }
            if (url.Contains(","))
            {
                url = url.Substring(0, url.IndexOf(','));
            }
            if ((url.Length >= 2 && url[0] == '~') && (url[1] == '/' || url[1] == '\\'))
            {
                return appPath + url.Substring(2);
            }
            if (!url.Contains("http://"))
            {
                if (url.StartsWith("/")) url = url.Substring(1);
                return appPath + url;
            }
            return url;
        }

        #endregion

        #region "    Properties        "

        internal IDictionary ChildNodeCollectionTable
        {
            get
            {
                if (this._childNodeCollectionTable == null)
                {
                    lock (_lock)
                    {
                        if (this._childNodeCollectionTable == null)
                        {
                            this._childNodeCollectionTable = new Hashtable();
                        }
                    }
                }
                return this._childNodeCollectionTable;
            }
        }

        internal IDictionary KeyTable
        {
            get
            {
                if (this._keyTable == null)
                {
                    lock (_lock)
                    {
                        if (this._keyTable == null)
                        {
                            this._keyTable = new Hashtable();
                        }
                    }
                }
                return this._keyTable;
            }
        }

        internal IDictionary ParentNodeTable
        {
            get
            {
                if (this._parentNodeTable == null)
                {
                    lock (_lock)
                    {
                        if (this._parentNodeTable == null)
                        {
                            this._parentNodeTable = new Hashtable();
                        }
                    }
                }
                return this._parentNodeTable;
            }
        }

        internal IDictionary UrlTable
        {
            get
            {
                if (this._urlTable == null)
                {
                    lock (_lock)
                    {
                        if (this._urlTable == null)
                        {
                            this._urlTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
                        }
                    }
                }
                return this._urlTable;
            }
        }

        protected SPWeb Web
        {
            get
            {
                return SPControl.GetContextWeb(HttpContext.Current);
            }
        }

        #endregion

    }


}
